home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
MacWorld 1998 September
/
Macworld (1998-09).dmg
/
Shareware World
/
Info
/
For Developers
/
MacZoop 1.8.3
/
Required Classes
/
Z Sources
/
ZApplication.cpp
< prev
next >
Wrap
Text File
|
1998-07-10
|
51KB
|
1,949 lines
/*************************************************************************************************
*
*
* MacZoop - "the framework for the rest of us"
*
*
*
* ZApplication.cpp -- the application object
*
*
*
*
*
* © 1996, Graham Cox
*
*
*
*
*************************************************************************************************/
#include "MacZoop.h"
#include "ProjectSettings.h"
#include "ZEventHandler.h"
#include "ZWindow.h"
#include "ZPrinter.h"
#include "ZUndoTask.h"
#if APPEARANCE_MGR_AWARE
#include <Appearance.h>
#endif
#if _USE_NAVIGATION_SERVICES
#include <Navigation.h>
#endif
#include <StandardFile.h>
#include <Notification.h>
// gApplication is the global application object. There is only one, naturally.
ZApplication* gApplication = NULL;
// globals inited by this class
ZMenuBar* gMenuBar = NULL; // the main menubar object
OSType gAppSignature; // the application's signature, obtained from 'BNDL', etc.
tMacInfo gMacInfo; // common gestalt results
RgnHandle gUtilRgn = NULL; // general purpose region, handy as temp variable. Do NOT dispose!
ZPrefsFile* gPrefsFile = NULL; // NOT inited unless your application subclass does it!
// static functions used by the application object
static pascal long ZGrowFunc( Size bytesShort );
static GrowZoneUPP gGZFunc = NewGrowZoneProc( ZGrowFunc );
#if _USE_NAVIGATION_SERVICES
static pascal void ZNavEventCallback( NavEventCallbackMessage cbMessage,
NavCBRecPtr cbParams,
NavCallBackUserData cbData );
NavEventUPP gNavEventHandler = NewNavEventProc( ZNavEventCallback );
#endif
// note: ZApplication is NEVER instantiated from a stream- to ensure this but keep
// special cases out of the class registry, etc, we jig the construction function
// to simply return gApplication.
ZObject* CF_ZApplication()
{
return gApplication;
}
extern ZObject* CF_ZObjectList();
/*--------------------------------*** CONSTRUCTOR ***---------------------------------*/
ZApplication::ZApplication()
: ZCommander( NULL )
{
long qdFeatures;
OSErr theErr;
classID = CLASS_ZApplication;
gUtilRgn = NewRgn();
done = FALSE;
phase = kInitialising;
zEH = NULL;
curUndoTask = NULL;
itsPrinter = NULL;
appResRefNum = CurResFile();
// set initial filetypes list to a zero handle
itsFileTypes = (FTypeListHdl) NewHandleClear( sizeof( FTypeList ) - sizeof( OSType ));
shortageFund = NULL;
memIsShort = FALSE;
userHasSeenAlert = FALSE;
// check to see if we are running on a colour Mac
theErr = Gestalt( gestaltQuickdrawFeatures, &qdFeatures );
gMacInfo.supportsColour = ((theErr == noErr) && (qdFeatures & 1));
// check for the drag manager
theErr = Gestalt( gestaltDragMgrAttr, &qdFeatures );
gMacInfo.hasDragManager = ((theErr == noErr) && (qdFeatures & 1));
// check for a FPU
theErr = Gestalt( gestaltFPUType, &qdFeatures );
gMacInfo.hasFPU = ((theErr == noErr) && ((qdFeatures & 1) == 0));
// check for applescript
theErr = Gestalt( gestaltAppleEventsAttr, &qdFeatures );
gMacInfo.hasAppleEvents = ((theErr == noErr) && (qdFeatures & 1));
// check for QuickTime™
theErr = Gestalt( gestaltQuickTimeVersion, &qdFeatures );
gMacInfo.hasQuickTime = ((theErr == noErr) && (qdFeatures > 0));
// check for ICM
theErr = Gestalt( gestaltCompressionMgr, &qdFeatures );
gMacInfo.hasImgCompressionMgr = (theErr == noErr);
// check for appearance manager
#if APPEARANCE_MGR_AWARE
theErr = Gestalt( gestaltAppearanceAttr, &qdFeatures );
gMacInfo.hasAppearanceMgr = (( theErr == noErr ) && ( qdFeatures & 1 ));
#if __powerc
if ( gMacInfo.hasAppearanceMgr )
gMacInfo.hasAppearanceMgr = ((long) CreateRootControl != kUnresolvedCFragSymbolAddress );
#endif
#else
gMacInfo.hasAppearanceMgr = FALSE;
#endif
// what is the system version?
theErr = Gestalt( gestaltSystemVersion, &qdFeatures );
gMacInfo.systemVersion = LoWord( qdFeatures );
// check for navigation services:
#if _USE_NAVIGATION_SERVICES
if ( gMacInfo.systemVersion >= 0x0755 )
gMacInfo.hasNavigationServices = NavServicesAvailable();
#else
gMacInfo.hasNavigationServices = FALSE;
#endif
// initialise the animating cursors, and set the
// animated watch cursor going.
AppCursorInit();
gApplication = this;
}
/*--------------------------------*** DESTRUCTOR ***---------------------------------*/
ZApplication::~ZApplication()
{
if ( gPrefsFile )
ForgetObject( gPrefsFile );
if ( zEH )
ForgetObject( zEH );
if ( itsPrinter )
ForgetObject( itsPrinter );
if ( curUndoTask )
ForgetObject( curUndoTask );
if ( itsFileTypes )
DisposeHandle((Handle) itsFileTypes);
ForgetObject( gMenuBar );
StopCursorAnimation();
}
/*--------------------------------*** INITMACZOOP ***---------------------------------*/
/*
Initialises the application, makes the event handler object and the menubar
----------------------------------------------------------------------------------------*/
void ZApplication::InitMacZoop( const short numMasterBlocks )
{
if ( phase == kInitialising )
{
// init the mac toolbox, memory mangler etc.
RegisterClasses();
InitMacApplication( numMasterBlocks );
// create the memory shortage fund
FailNIL( shortageFund = NewHandle( kShortageFundSize ));
SetGrowZone( gGZFunc );
SetWatchCursor();
// make the event handler object
MakeEventHandler();
// make the clipboard object
MakeClipboard();
// read any prefs that the user may have set up (default does nothing). A typical
// thing to do here is to simply instantiate a ZPrefsFile object and assign it to
// <gPrefsFile>. You may also want to open/read the file! (use of data or resource
// fork is up to you, so ZPrefsFile does not automatically perform the open step).
ReadPrefs();
// initialise menubar and make printer object
InitMenuBar();
MakePrinter();
// preload the navigation services if required, to make use of these faster later on.
#if _USE_NAVIGATION_SERVICES
if ( gMacInfo.hasNavigationServices )
FailOSErr( NavLoad());
#endif
// call user-function as last part of initialisation- default
// does nothing but can be overridden to do further set up.
StartUp();
// update the menubar- note that this is not drawn until StartUp completes,
// since we allow the programmer the flexibility to use StartUp to create & install
// dynamic menus, etc. if they want.
gMenuBar->UpdateMenuBar();
// the cursor is not reset here- we let the main event loop do that so that if
// events are initially available, the cursor keeps right on animating until
// the user gets a chance to do anything.
phase = kRunning;
}
}
/*-----------------------------*** INITMACAPPLICATION ***-----------------------------*/
/*
Initialises the mac toolbox and memory manager
----------------------------------------------------------------------------------------*/
void ZApplication::InitMacApplication( const short numMasterBlocks )
{
// chant the "Macintosh mantra"...
InitGraf( &qd.thePort );
InitFonts();
InitWindows();
InitMenus();
TEInit();
InitDialogs( NULL );
// clear out the event queue in case any stray clicks or keypresses are left there
FlushEvents( everyEvent, 0 );
// see if the program can run on this Mac- if not, we show an alert and exit straight away
if (! CheckCanRun())
{
StopCursorAnimation();
(void) Alert( kCantRunAlertID, NULL );
ExitToShell();
}
else
{
// give yourself enough memory. For a bigger app, you may need to call MoreMasters a few
// more times. The parameter to this method sets the number of times MoreMasters is
// called, defaulting to 8, which gives 8 x 64 = 512 handles.
MaxApplZone();
short n = numMasterBlocks;
while( n-- )
MoreMasters();
// if we want appearance, register the app:
#if APPEARANCE_MGR_AWARE
FailOSErr( RegisterAppearanceClient());
#endif
// set up <gAppSignature>, either from BNDL resource, or from constant according
// to the user's project settings.
#if USE_SIGNATURE_FROM_BNDL
OSType** bndlHand = (OSType**) GetResource( 'BNDL', 128 );
if ( bndlHand )
{
gAppSignature = **bndlHand;
ReleaseResource((Handle) bndlHand );
#if CHECK_FREF_RESOURCE_TYPES
// first look for an 'open' resource ID=128. If there is one, we use it in
// preference to the FREF's, since there is a slightly different meaning. An 'open'
// resource can be treated as a list of OSTypes if you ignore the first 8 bytes.
bndlHand = (OSType**) GetResource( 'open', 128 );
if ( bndlHand )
{
// we have an 'open' resource, so just copy the data (ignoring first 8 bytes)
// to the file types list. Warning: This technique does not check for duplicates,
// either in the resource or in the types list.
long bc = GetHandleSize((Handle) bndlHand );
SetHandleSize((Handle) itsFileTypes, bc );
FailMemError();
BlockMoveData((Ptr) *bndlHand, (Ptr) *itsFileTypes, bc );
ReleaseResource((Handle) bndlHand );
}
else
{
// scan the 'FREF' resources and build the <itsFileTypes> list. We do NOT add
// types of 'APPL', 'cdev', 'INIT', or 'rdev'- if you want to display such
// files in the Open dialog, you have to call AddFileType() for these types directly.
// This is a feature to prevent weird behaviour in the typical case.
// how many 'FREF's do we have?
short i, fc = Count1Resources( 'FREF' );
for( i = 1; i <= fc; i++ )
{
bndlHand = (OSType**) GetIndResource( 'FREF', i );
if ( bndlHand )
{
// check if this is one of our 'forbidden' types:
if ( **bndlHand != 'APPL' &&
**bndlHand != 'APPC' &&
**bndlHand != 'APPD' &&
**bndlHand != 'cdev' &&
**bndlHand != 'rdev' &&
**bndlHand != 'INIT' )
AddFileType( **bndlHand );
ReleaseResource((Handle) bndlHand );
}
}
}
#endif
}
else
gAppSignature = kUnknownSignature;
#else
gAppSignature = kApplicationSignature;
#endif
(*itsFileTypes)->appSignature = gAppSignature;
}
}
/*--------------------------------*** CHECKCANRUN ***---------------------------------*/
/*
Examine the machine environment to see if this will run on this Mac. By default, it can
on System 7 or later.
----------------------------------------------------------------------------------------*/
Boolean ZApplication::CheckCanRun()
{
// Returning FALSE will cause the program to
// immediately show an alert and quit.
// by default, we can run on any Mac with System 7.0 or later.
return( gMacInfo.systemVersion >= 0x0700 );
}
#pragma mark -
/*----------------------------------*** GETNAME ***-----------------------------------*/
/*
Get the user-visible name of the application, as seen in the Finder. This works even if
the user has renamed the app, since it finds the filename.
----------------------------------------------------------------------------------------*/
void ZApplication::GetName( Str255 appName )
{
StringPtr sp = LMGetCurApName();
CopyPString( sp, appName );
}
/*------------------------------------*** RUN ***-------------------------------------*/
/*
Fetch events and handle them until the user quits
----------------------------------------------------------------------------------------*/
void ZApplication::Run()
{
// runs the show by asking the event object to get events and handle them. This goes on
// forever until done is set to TRUE.
if ( phase == kRunning )
{
while(! done)
{
try
{
// repeatedly get an event, handle an event
Process1Event();
// deal with the memory shortage situation, if one has arisen as
// a result of the last event.
CheckLowMemory();
}
catch( OSErr theErr )
{
// if here, an exception was thrown. If the error was userCanceledErr,
// the alert is not shown, since the exception is legitimate. Thus for
// many operations, you need only throw a userCanceledErr exception to
// simply invoke the cancel.
StopCursorAnimation();
HandleError( theErr );
// the buck stops here- no exceptions will be thrown beyond this point.
}
// stop any animating cursors. This means that a lengthy process need
// only set the cursor going and can then forget about it. When the
// app resumes handling events, it will be automatically cancelled.
StopCursorAnimation();
}
done = TRUE;
phase = kQuitting;
}
}
/*-----------------------------*** CHECKLOWMEMORY ***---------------------------------*/
/*
check if a low memory situation has arisen. If so, inform the user and manage the re-
plenishment of the shortage fund.
----------------------------------------------------------------------------------------*/
void ZApplication::CheckLowMemory()
{
if ( memIsShort )
{
// some of the shortage fund was used. Try to replenish it:
memIsShort = FALSE;
Handle temp = NewHandle( kShortageFundSize );
// if that resets memIsShort, then the grow zone func was called, so we
// are not in the clear yet. However, if the grow zone func wasn't
// called, then we can safely get rid of that handle and replace it with
// this one
if ( memIsShort )
{
if ( temp )
DisposeHandle( temp );
// couldn't replenish the fund, so if the user hasn't seen the
// warning yet, show it now.
if (! userHasSeenAlert)
{
StopCursorAnimation();
(void) NotifyAlert( kMemoryLowAlertID );
userHasSeenAlert = TRUE;
}
// if memIsShort is TRUE, it affects UpdateMenus such that New and Open are
// greyed out. This is an attempt to stop the user creating things that will
// eat up even more memory. You might want to use the same technique for commands
// of your own that may allocate lots of memory. For this reason, memIsShort is
// a public member.
}
else
{
// fund replenished, so get rid of any remaining fund and replace it
// with the newly allocated shortage fund.
DisposeHandle( shortageFund );
shortageFund = temp;
userHasSeenAlert = FALSE;
}
}
}
/*------------------------------*** MEMORYSHORTAGE ***--------------------------------*/
/*
called from the growzone proc when memory needs to be freed. This releases some or all
of the shortage fund, but you can override it to make additional sacrifices if need be.
----------------------------------------------------------------------------------------*/
Boolean ZApplication::MemoryShortage( const Size bytesShort )
{
// this is called when the memory manager gets into dire straits. We can free some or all
// of our emergency fund to satisfy the request. If we succeed, we return TRUE, else FALSE.
Size fundSize = 0;
Handle gzHandle;
// get the handle the mem manager is dealing with at the moment. This is important since
// this might be the shortage fund itself. If it is, we can't resize it, so we really
// are in deep do-do. In this case, we flag memIsShort and hope the user will not ignore
// the message!
gzHandle = GZSaveHnd();
if ( gzHandle != shortageFund )
{
fundSize = GetHandleSize( shortageFund );
// release all or some of the memory to try and satisfy the request
if ( fundSize <= bytesShort )
SetHandleSize( shortageFund, 0 );
else
SetHandleSize( shortageFund, fundSize - bytesShort );
}
// flag the shortage so we can inform the user
memIsShort = TRUE;
// did we actually manage to free the requested amount?
return( fundSize > bytesShort );
}
/*------------------------------*** PROCESS1EVENT ***---------------------------------*/
/*
get an event, dispatch an event, get an event, dispatch...
----------------------------------------------------------------------------------------*/
void ZApplication::Process1Event()
{
EventRecord theEvent;
zEH->GetAnEvent( &theEvent );
zEH->DispatchAnEvent( &theEvent );
}
/*------------------------------*** PROCESS1EVENT ***---------------------------------*/
/*
dispatch an event that was fetched externally, for example by a library such as Nav
Services.
----------------------------------------------------------------------------------------*/
void ZApplication::Process1Event( EventRecord* anExternalEvent )
{
zEH->DispatchAnEvent( anExternalEvent );
}
/*-----------------------------*** GETCURRENTEVENT ***--------------------------------*/
/*
return the current event in progress, with FALSE if it was a null event, else TRUE.
----------------------------------------------------------------------------------------*/
Boolean ZApplication::GetCurrentEvent( EventRecord* anEvent )
{
zEH->GetLatestEvent( anEvent );
return( anEvent->what != nullEvent );
}
/*--------------------------------*** GETCLICKS ***-----------------------------------*/
/*
return the number of clicks counted by the event handler in the same place. Returns 1 for
single click, 2 for double, 3 for triple, etc.
----------------------------------------------------------------------------------------*/
short ZApplication::GetClicks()
{
return zEH->GetClicks();
}
/*-------------------------------*** INBACKGROUND ***---------------------------------*/
/*
return TRUE if the application is in the background, FALSE in foreground.
----------------------------------------------------------------------------------------*/
Boolean ZApplication::InBackground()
{
return zEH->InBackground();
}
/*-----------------------------------*** QUIT ***-------------------------------------*/
/*
close all of the windows. If successful, delete the application and return TRUE
----------------------------------------------------------------------------------------*/
Boolean ZApplication::Quit()
{
if ( phase == kQuitting )
{
CloseAll();
// the Quit can be abandoned by resetting <done> to FALSE. If this
// has not occurred, then truly say goodbye.
if ( done )
{
#if _USE_NAVIGATION_SERVICES
if ( gMacInfo.hasNavigationServices )
FailOSErr( NavUnload());
#endif
#if APPEARANCE_MGR_AWARE
(void) UnregisterAppearanceClient();
#endif
ShutDown();
ForgetThis();
}
}
return done;
}
/*-------------------------------*** REQUESTQUIT ***----------------------------------*/
/*
ask the application to quit. This is called by choosing Quit from the File menu, for example.
----------------------------------------------------------------------------------------*/
void ZApplication::RequestQuit()
{
done = TRUE;
}
/*----------------------------*** HANDLEAPPLEEVENT ***--------------------------------*/
/*
Handle the four required apple events. If you override this to handle your own apple
events, be sure to call the inherited method for everything else.
----------------------------------------------------------------------------------------*/
void ZApplication::HandleAppleEvent( AEEventClass aeClass, AEEventID aeID,
AppleEvent* aeEvt, AppleEvent* reply )
{
if ( aeClass == kCoreEventClass )
{
FSSpec aFile;
AEDescList docList;
long i, n;
AEKeyword keyWrd;
DescType retType;
Size actualSize;
FInfo fi;
switch ( aeID )
{
case kAEReopenApplication:
if ( GetFrontWindow())
break;
// fall through to normal open app event case if there are no windows:
case kAEOpenApplication:
#if MAKE_UNTITLED_STARTUP_WINDOW
OpenNewWindow();
#endif
break;
case kAEOpenDocuments:
FailOSErr( AEGetParamDesc( aeEvt, keyDirectObject, typeAEList, &docList ));
FailOSErr( AECountItems( &docList, &n ));
// get each document and open it
try
{
for (i = 1; i <= n; i++)
{
FailOSErr( AEGetNthPtr( &docList,
i,
typeFSS,
&keyWrd,
&retType,
&aFile,
sizeof( FSSpec ),
&actualSize ));
FailOSErr( FSpGetFInfo( &aFile, &fi ));
// open the file into a window and select it, provided
// it really is one we can open. If the file is a prefs file, call up
// the kDoPreferences command:
if ( fi.fdType == 'pref' )
HandleCommand( kCmdDoPreferences );
else
{
if ( CanOpenFileType( fi.fdType ))
OpenFile( aFile, fi.fdType, ( fi.fdFlags & kIsStationery ) == kIsStationery );
}
}
}
catch( OSErr err )
{
AEDisposeDesc( &docList );
throw err;
}
FailOSErr( AEDisposeDesc( &docList ));
break;
case kAEPrintDocuments:
FailOSErr( AEGetParamDesc( aeEvt, keyDirectObject, typeAEList, &docList ));
FailOSErr( AECountItems( &docList, &n ));
// ask the application to show the page setup dialog
if ( n > 0 )
DoPageSetup();
// get each document passed by the finder
try
{
for (i = 1; i <= n; i++)
{
FailOSErr( AEGetNthPtr( &docList,
i,
typeFSS,
&keyWrd,
&retType,
&aFile,
sizeof( FSSpec ),
&actualSize ));
FailOSErr( FSpGetFInfo( &aFile, &fi ));
// open the file into a window and select it
if ( CanOpenFileType( fi.fdType ))
{
OpenFile( aFile, fi.fdType, ( fi.fdFlags & kIsStationery ) == kIsStationery );
// update the window (not strictly needed, but looks better)
zEH->HandleWindowUpdate( mostRecent->GetMacWindow());
// ask the application to print the document
DoPrint();
// that done, we can close the window and move on to the next
mostRecent->Close( GetPhase());
}
}
}
catch( OSErr err )
{
AEDisposeDesc( &docList );
throw err;
}
FailOSErr( AEDisposeDesc( &docList ));
break;
case kAEQuitApplication:
RequestQuit();
break;
default:
ZCommander::HandleAppleEvent( aeClass, aeID, aeEvt, reply );
break;
}
}
else
ZCommander::HandleAppleEvent( aeClass, aeID, aeEvt, reply );
}
/*-------------------------------*** HANDLEERROR ***----------------------------------*/
/*
display an alert indicating the error (some errors do nothing, likewise noErr is ignored)
----------------------------------------------------------------------------------------*/
void ZApplication::HandleError( OSErr theErr )
{
// handles the error passed by displaying an alert. This is called by the exception
// handler for the application, but you can call it at any time. Some errors are "silent"
// in that the exception does not result in a message. These include userCanceled, kSilent
// Err, and aeEventNotHandled. Override this if you want to handle errors differently.
gMenuBar->SetTitleHilite( 0, FALSE );
if ( theErr != userCanceledErr &&
theErr != kSilentErr &&
theErr != noErr &&
theErr != errAEEventNotHandled )
{
StringHandle errExpH;
Str255 errMsgStr;
Str31 errExplStr;
Str15 errIDStr;
NumToString( theErr, errIDStr );
// try to build a meaningful error message by looking for an 'Estr'
// resource with the same iD as the error. If found, this is concatenated
// onto the generic error stub and displayed. If not found, the default
// explanation "an error occurred" is used.
GetIndString( errMsgStr, 128, 10 );
errExpH = (StringHandle) GetResource( 'Estr', theErr );
if ( errExpH )
{
ConcatPStrings( errMsgStr, *errExpH );
ReleaseResource((Handle) errExpH );
}
else
{
GetIndString( errExplStr, 128, 11 );
ConcatPStrings( errMsgStr, errExplStr );
}
ParamText( errIDStr, errMsgStr, NULL, NULL );
(void) NotifyAlert( kExceptionAlertID, ntMinimalAlert );
}
}
/*------------------------*** WAITAPPLICATIONFOREGROUND ***---------------------------*/
/*
handle events until the application is not in the background. Used by NotifyAlert().
----------------------------------------------------------------------------------------*/
void ZApplication::WaitApplicationForeground()
{
while( InBackground())
Process1Event();
}
#pragma mark -
/*------------------------------*** HANDLECOMMAND ***---------------------------------*/
/*
Handle commands (menu items) that apply to the application as a whole, like Quit.
----------------------------------------------------------------------------------------*/
void ZApplication::HandleCommand( const long aCmd )
{
// handle commands at the application level. This includes quit, new, open, etc.
FSSpec aFile;
OSType anFType;
FInfo fi;
switch( aCmd )
{
case kCmdAbout:
AboutBox();
break;
case kCmdNew:
OpenNewWindowType( kDefaultWindowType ); // open a new "untitled" window
break;
case kCmdOpen:
if ( PickFile( &aFile, &anFType )) // choose a file
{
// is it a stationery (template) file?
FSpGetFInfo( &aFile, &fi );
OpenFile( aFile, anFType, ( fi.fdFlags & kIsStationery ) == kIsStationery ); // open the file into a window
}
break;
case kCmdQuit:
RequestQuit(); // the app should now quit
break;
case kCmdPageSetup:
DoPageSetup();
break;
case kCmdPrint:
DoPrint();
break;
case kCmdUndo:
if ( curUndoTask ) // undo item available?
{
SetWatchCursor();
if (curUndoTask->IsUndone()) // undo or redo?
curUndoTask->Redo(); // redo the task
else
curUndoTask->Undo(); // undo the task
}
break;
case kCmdDoPreferences:
DoPreferences();
break;
}
}
/*------------------------------*** HANDLECOMMAND ***---------------------------------*/
void ZApplication::HandleCommand( const short menuID, const short itemID )
{
GrafPtr savePort;
Str255 daName;
if ( menuID == kAppleMenuID &&
itemID > 2 )
{
// open desk accessories.
GetMenuItemText( GetMenuHandle( menuID ), itemID, daName );
GetPort( &savePort );
OpenDeskAcc( daName );
SetPort( savePort );
}
}
/*-------------------------------*** UPDATEMENUS ***----------------------------------*/
/*
enable the menu commands that the app can handle at the moment.
----------------------------------------------------------------------------------------*/
void ZApplication::UpdateMenus()
{
// enable the menu commands that pertain to the application. This includes "New",
// "Quit" and "About" amongst others
// apple menu
gMenuBar->EnableCommand( kCmdAbout );
// file menu
// if memory is currently short, do not enable new or open, since those are the
// commands that are likely to allocate a lot more memory, which we do not have.
if (! memIsShort)
{
gMenuBar->EnableCommand( kCmdNew );
gMenuBar->EnableCommand( kCmdOpen );
}
gMenuBar->EnableCommand( kCmdQuit );
// enable the printing items if there is a printer and the front window
// supports printing.
if ( itsPrinter )
{
gMenuBar->EnableCommand( kCmdPageSetup );
ZWindow* fWindow = GetFrontWindow();
if (fWindow && fWindow->IsPrintable())
gMenuBar->EnableCommand( kCmdPrint );
}
// edit:
UpdateUndo();
gMenuBar->EnableCommand( kCmdDoPreferences );
}
/*-------------------------------*** DOPAGESETUP ***----------------------------------*/
/*
handle the Page Setup command
----------------------------------------------------------------------------------------*/
void ZApplication::DoPageSetup()
{
if ( itsPrinter )
{
gWindowManager->Deactivate();
itsPrinter->PageSetUp();
gWindowManager->Activate();
}
}
/*----------------------------------*** DOPRINT ***-----------------------------------*/
/*
handle the Print command
----------------------------------------------------------------------------------------*/
void ZApplication::DoPrint()
{
if ( itsPrinter )
{
ZWindow* aWindow = GetFrontWindow();
gWindowManager->Deactivate();
itsPrinter->Print( aWindow );
gWindowManager->Activate();
}
}
/*---------------------------------*** ABOUTBOX ***-----------------------------------*/
/*
display the application's about box
----------------------------------------------------------------------------------------*/
void ZApplication::AboutBox()
{
gWindowManager->DeactivateForDialog( kAboutBoxID, TRUE );
(void) Alert( kAboutBoxID, NULL );
gWindowManager->Activate();
}
/*----------------------------------*** SETTASK ***-----------------------------------*/
/*
update the current global undo task
----------------------------------------------------------------------------------------*/
void ZApplication::SetTask( ZUndoTask* aTask )
{
if ( curUndoTask )
ForgetObject( curUndoTask );
curUndoTask = aTask;
}
/*--------------------------------*** UPDATEUNDO ***----------------------------------*/
/*
update the Undo menu item to reflect the current undo task
----------------------------------------------------------------------------------------*/
void ZApplication::UpdateUndo()
{
Str255 undoMenuStr;
if ( curUndoTask )
{
// a task available, so update the undo menu item to the task string.
Str63 taskStr;
if (curUndoTask->IsUndone())
GetIndString( undoMenuStr, kMiscStrListID, kRedoStrIndex );
else
GetIndString( undoMenuStr, kMiscStrListID, kUndoStrIndex );
// add the task name
curUndoTask->GetTaskString( taskStr );
ConcatPStrings( undoMenuStr, taskStr );
// set the menu item to the task name
gMenuBar->SetCommandText( kCmdUndo, undoMenuStr );
// there is a task, but does it refer to the front window? If not,
// then do not actually enable the command.
if ((curUndoTask->GetUndoTarget() == GetFrontWindow()) &&
(curUndoTask->GetUndoTarget() != NULL))
gMenuBar->EnableCommand( kCmdUndo );
}
else
{
// if no task, just show a dimmed "Can't Undo"
gMenuBar->SetCommandText( kCmdUndo, kMiscStrListID, kCantUndoStrIndex );
}
}
#pragma mark -
/*-------------------------------*** INITMENUBAR ***----------------------------------*/
/*
set up the menubar object for managing the main menus
----------------------------------------------------------------------------------------*/
void ZApplication::InitMenuBar()
{
// installs the menu bar. By default, we just install 'MBAR' ID = 128, which means you
// don't need to override this to get other menus- just create the resources you want.
FailNIL( gMenuBar = new ZMenuBar( kStdMenubarID ));
gMenuBar->InitMenuBar();
}
/*---------------------------*** MOUSENOTINANYWINDOW ***------------------------------*/
/*
the mouse is not over any window. By default this just sets the default cursor shape, but
you can override this to get informed if you are interested in this fact.
----------------------------------------------------------------------------------------*/
void ZApplication::MouseNotInAnyWindow( const Point globalMouse )
{
ResumeCursorAnimation();
SetCursorShape( 0 );
}
/*--------------------------------*** MAKECLIPBOARD ***-------------------------------*/
/*
Make the clipboard object. By default, this is a ZClipboard.
----------------------------------------------------------------------------------------*/
void ZApplication::MakeClipboard()
{
FailNIL( gClipboard = new ZClipboard());
}
/*------------------------------*** MAKEEVENTHANDLER ***------------------------------*/
/*
Make the event handler object. By default, this is a ZEventHandler. This also makes the
window manager, since that is functionally related to the event handler.
----------------------------------------------------------------------------------------*/
void ZApplication::MakeEventHandler()
{
// make the event handler
FailNIL( zEH = new ZEventHandler());
// make the window manager for handling floating windows, etc.
FailNIL( gWindowManager = new ZWindowManager());
// install handlers for the four required events
zEH->InstallApplescriptHandlers();
}
/*--------------------------------*** MAKEPRINTER ***---------------------------------*/
/*
creates a new printer object for handling the print commands
----------------------------------------------------------------------------------------*/
void ZApplication::MakePrinter()
{
#if PRINTING_ON
try
{
FailNIL( itsPrinter = new ZPrinter());
}
catch( OSErr err )
{
itsPrinter = NULL;
// note, to not have a printer is not fatal- in this case we carry on,
// but print commands will be greyed out. This may happen if no printer
// driver is selected in the chooser.
}
#endif
}
#pragma mark -
/*------------------------------*** MAKENEWWINDOW ***---------------------------------*/
/*
creates a new window object and initialises it. It does not show it yet. Normally you will
override this method to create your own useful kinds of windows. However, even if you don't
you'll still get a window that works. Eat your heart out Sprocket!
----------------------------------------------------------------------------------------*/
void ZApplication::MakeNewWindow()
{
FailNIL( mostRecent = new ZWindow( this, kUntitledWindowID ));
try
{
mostRecent->InitZWindow();
}
catch( OSErr err )
{
ForgetObject( mostRecent );
throw err;
}
}
/*------------------------------*** OPENNEWWINDOW ***---------------------------------*/
/*
creates a new window object and initialises it. It then shows it and makes it active.
----------------------------------------------------------------------------------------*/
void ZApplication::OpenNewWindow()
{
ZWindow* theWindow;
gMenuBar->SetZoomSourceToCommand( kCmdNew );
theWindow = mostRecent = MakeNewWindowType( kDefaultWindowType );
if ( theWindow )
{
theWindow->Place();
theWindow->Select();
}
}
/*----------------------------*** OPENNEWWINDOWTYPE ***-------------------------------*/
/*
create a new window object of the requisite type. This is the modern version of
OpenNewWindow(), but older code will work as expected.
----------------------------------------------------------------------------------------*/
ZWindow* ZApplication::OpenNewWindowType( OSType aType )
{
ZWindow* theWindow;
gMenuBar->SetZoomSourceToCommand( kCmdNew );
theWindow = mostRecent = MakeNewWindowType( aType );
if ( theWindow )
{
theWindow->Place();
theWindow->Select();
}
return theWindow;
}
/*---------------------------------*** CLOSEALL ***-----------------------------------*/
/*
closes all of the application's windows. If one refuses to close, this will abort a Quit.
This iterates backwards through its underlings list looking for windows, rather than
relying on the window lists, since windows will also close their own underling windows so
the whole commander tree is cleaned up properly. If your app organises things differently,
you may need to override this.
----------------------------------------------------------------------------------------*/
void ZApplication::CloseAll( Boolean closeFloaters )
{
long i;
ZWindow* aWindow;
if ( itsUnderlings )
{
i = itsUnderlings->CountItems();
while( i )
{
aWindow = dynamic_cast<ZWindow*>( itsUnderlings->GetObject( i-- ));
if ( aWindow )
{
if ( phase == kQuitting )
{
if ( ! aWindow->Close( phase ))
{
phase = kRunning;
done = FALSE;
break;
}
}
else
{
if (! aWindow->NoAutoClose() && ( aWindow->Floats() == closeFloaters ))
{
if ( ! aWindow->Close( phase ))
{
phase = kRunning;
done = FALSE;
break;
}
}
}
}
}
}
}
/*------------------------------*** GETFRONTWINDOW ***--------------------------------*/
/*
returns the active window object, if there is one
----------------------------------------------------------------------------------------*/
ZWindow* ZApplication::GetFrontWindow()
{
return ( gWindowManager->GetTopWindow());
}
/*----------------------------------*** PICKFILE ***----------------------------------*/
/*
displays the standard file dialog for selecting a file, and returns its filespec.
----------------------------------------------------------------------------------------*/
Boolean ZApplication::PickFile( FSSpec* aFile, OSType* fType )
{
// uses standard file to choose a file to open to a window. By default, no files types
// are added to the list, which we here interpret to mean "show all files".
#if _USE_NAVIGATION_SERVICES
// using navigation services, so we need to tackle things a little differently. The result
// from this method is the same- namely a single filespec and a type.
if ( gMacInfo.hasNavigationServices )
{
OSErr theErr;
NavReplyRecord navReply;
NavDialogOptions navOptions;
Boolean result = FALSE;
FailOSErr( NavGetDefaultDialogOptions( &navOptions ));
GetName( navOptions.clientName );
// only permit one selection here:
navOptions.dialogOptionFlags &= ~kNavAllowMultipleFiles;
gWindowManager->Deactivate();
StopCursorAnimation();
theErr = NavGetFile( NULL,
&navReply,
&navOptions,
gNavEventHandler,
NULL,
NULL,
(NavTypeListHandle) itsFileTypes,
(NavCallBackUserData) this );
gWindowManager->Activate();
// extract info:
if (( theErr == noErr ) && navReply.validRecord )
{
long count, i;
AEDesc specDesc;
FInfo fi;
FailOSErr( AECountItems( &navReply.selection, &count ));
for( i = 1; i <= count; i++ )
{
FailOSErr( AEGetNthDesc( &navReply.selection, i, typeFSS, NULL, &specDesc ));
BlockMoveData( *specDesc.dataHandle, aFile, sizeof( FSSpec ));
// to get the type we need to do a FsGetFInfo:
FSpGetFInfo( aFile, &fi );
*fType = fi.fdType;
}
result = TRUE;
}
else
result = FALSE;
FailOSErr( NavDisposeReply( &navReply ));
return result;
}
else
{
#endif
StandardFileReply aReply;
OSType* listPtr;
short numTypes;
HLock((Handle) itsFileTypes );
numTypes = (*itsFileTypes )->osTypeCount;
listPtr = &(*itsFileTypes)->osType[0];
// if no types in the list, show all of them
if( numTypes <= 0 )
numTypes = -1;
StopCursorAnimation();
gWindowManager->DeactivateForDialog( sfGetDialogID );
// display the dialog
StandardGetFile( NULL, numTypes, listPtr, &aReply );
HUnlock((Handle) itsFileTypes );
gWindowManager->Activate();
if ( aReply.sfGood )
{
*aFile = aReply.sfFile;
*fType = aReply.sfType;
return TRUE;
}
else
return FALSE;
#if _USE_NAVIGATION_SERVICES
}
#endif
}
/*----------------------------------*** OPENFILE ***----------------------------------*/
/*
creates a new window and asks it to open the file. It then shows the window and activates it.
If your application doesn't have a classic "document" interface, but e.g. simply processes
files dropped on the application, you can override this method to process the files. It will
be called once for each file dropped on the application that we know how to open.
----------------------------------------------------------------------------------------*/
void ZApplication::OpenFile( const FSSpec& aFile, const OSType fType, Boolean isStationery )
{
// opens the file into a new window. This is equivalent to OpenNewWindow, but
// for when the user chose a file with the Open command
ZWindow* aWindow = NULL;
SetWatchCursor();
aWindow = mostRecent = MakeNewWindowType( fType );
// window created, so ask it to open the chosen file
if ( aWindow )
{
try
{
aWindow->SetFile( aFile );
aWindow->OpenFile( fType, isStationery );
aWindow->Place();
aWindow->Select();
}
catch( OSErr err )
{
ForgetObject( aWindow );
throw err;
}
}
}
/*--------------------------------*** ADDFILETYPE ***---------------------------------*/
/*
adds <aType> to the list of types this application will show in the Open dialog. You can
call this for each type your application can open. This ignores duplicates.
-----------------------------------------------------------------------------------------*/
void ZApplication::AddFileType( const OSType aType )
{
// adds the file type to the list of types, if not already there
short nTypes, i;
nTypes = (*itsFileTypes)->osTypeCount;
// check that the file type we are adding is unique in the list
for ( i = 0; i < nTypes; i++ )
{
if ((*itsFileTypes)->osType[i] == aType )
return;
}
// if we are still here, type is unique, so append it
// first grow the handle
SetHandleSize((Handle) itsFileTypes, GetHandleSize((Handle) itsFileTypes ) + sizeof( OSType ));
FailMemError();
// set the new entry
(*itsFileTypes)->osType[ nTypes ] = aType;
(*itsFileTypes)->osTypeCount++;
}
/*------------------------------*** CANOPENFILETYPE ***--------------------------------*/
/*
return TRUE if this application can open files of the given type. The default method
determines if the type is in the fileType list or not. If the list is empty, this always
return TRUE.
-----------------------------------------------------------------------------------------*/
Boolean ZApplication::CanOpenFileType( const OSType aType )
{
short nTypes, i;
nTypes = (*itsFileTypes)->osTypeCount;
if ( nTypes > 0 )
{
for ( i = 0; i < nTypes; i++ )
{
if ( (*itsFileTypes)->osType[i] == aType )
return TRUE;
}
return FALSE;
}
else
return TRUE;
}
/*------------------------------*** REGISTERCLASSES ***-------------------------------*/
/*
in order to use persistent objects, classes must be registered. You only need to register
classes that you will want to create from a stream. By default this registers those
required classes that could be constructed from a stream- override this to extend the
range of classes, but always call the inherited method.
----------------------------------------------------------------------------------------*/
void ZApplication::RegisterClasses()
{
#if _MACZOOP_STREAMS
FailNIL( gClasses );
REGISTERCLASS( ZComrade );
REGISTERCLASS( ZCommander );
REGISTERCLASS( ZArray );
REGISTERCLASS( ZWindow );
REGISTERCLASS( ZApplication );
REGISTERCLASS( ZObjectList );
#endif
}
#pragma mark -
/*---------------------------------*** ZGROWFUNC ***----------------------------------*/
/*
memory manager callback proc.
----------------------------------------------------------------------------------------*/
static pascal long ZGrowFunc( Size bytesShort )
{
Boolean bytesFreed;
Size growBytes;
// call application object to free some memory
try
{
bytesFreed = gApplication->MemoryShortage( bytesShort );
// try to compact and purge the memory after the
// application has done something to free some up.
(void) MaxMem( &growBytes );
}
catch( OSErr err )
{
bytesFreed = 0;
}
return bytesFreed;
}
/******************************************************************************
CopyPString
Copy a Pascal string
******************************************************************************/
void CopyPString( ConstStr255Param srcString, Str255 destString )
{
BlockMoveData( srcString, destString, MIN( srcString[0] + 1, 255 ));
}
/******************************************************************************
ConcatPStrings
Concatenate two Pascal strings by attaching the second string on
the end of the first string.
******************************************************************************/
void ConcatPStrings( Str255 root, ConstStr255Param append )
{
short charsToCopy;
// Truncate if concatenated string would be longer than 255 chars.
charsToCopy = MIN( append[0], 255 - root[0]);
BlockMoveData( append + 1, root + root[0] + 1, (long) charsToCopy);
root[0] += charsToCopy;
}
/******************************************************************************
CopyPStringTrunc
Copy a Pascal string, limiting length to <ccLim> characters. This is
handy for copying a Str255 to a shorter type, e.g. Str31.
******************************************************************************/
void CopyPStringTrunc( ConstStr255Param srcString, Str255 destString, unsigned char ccLim )
{
BlockMoveData( srcString, destString, MIN( srcString[0] + 1, ccLim + 1 ));
destString[0] = MIN( srcString[0], ccLim );
}
/******************************************************************************
ConcatPStringsTrunc
Concatenate two Pascal strings by attaching the second string on
the end of the first string, but limiting the overall length to <ccLim>
******************************************************************************/
void ConcatPStringsTrunc( Str255 root, ConstStr255Param append, unsigned char ccLim )
{
short charsToCopy;
// Truncate if concatenated string would be longer than <ccLim> chars.
charsToCopy = MIN( append[0], ccLim - root[0]);
BlockMoveData( append + 1, root + root[0] + 1, (long) charsToCopy);
root[0] += charsToCopy;
}
/*------------------------------*** RUNAPPLICATION ***--------------------------------*/
/*
standard function to run the application. Your main() function must make the relevant
ZApplication object, assign it to gApplication, then call this.
----------------------------------------------------------------------------------------*/
void RunApplication()
{
if (gApplication)
{
try
{
gApplication->InitMacZoop(); // initialise the whole kaboodle. This is
// NOT done by the constructor since you might
// want to override the initialisation.
}
catch( OSErr err )
{
// if an exception occurs during startup, the application cannot run, since
// everything must be properly built and in place before handling events. In this
// case we display a fatal alert message and exit.
Str31 appName, errStr;
gApplication->GetName( appName );
NumToString( err, errStr );
ParamText( appName, errStr, NULL, NULL );
(void) Alert( kFatalStartupErrAlertID, NULL );
ExitToShell();
}
// initialisation is now complete, so we can go ahead and run the thing
do
{
gApplication->Run(); // run the application until the user quits
}
while (! gApplication->Quit()); // try to quit
}
}
Boolean IsColourPort( GrafPtr aPort )
{
return (( aPort->portBits.rowBytes & 0x8000 ) != 0 );
}
void SetHiliteMode()
{
LMSetHiliteMode( LMGetHiliteMode() & pHiliteBit );
}
/*----------------------------------*** MACHASDM ***----------------------------------*/
/*
static function returns TRUE if Drag Manager is present. Checks link on PowerMacs.
----------------------------------------------------------------------------------------*/
Boolean MacHasDM()
{
// returns TRUE if drag manager is installed on this mac and is loaded by the CFM.
Boolean hasDM = gMacInfo.hasDragManager;
#if GENERATINGCFM
// check that the dragLib is actually loaded and linked
hasDM = hasDM && ( NewDrag != (void*) kUnresolvedCFragSymbolAddress );
#endif
return hasDM;
}
/*--------------------------------*** NOTIFYALERT ***---------------------------------*/
/*
Works just like Alert(), except that if the app is in the background, the notification
manager is used to inform the user that the app requires attention.
----------------------------------------------------------------------------------------*/
static NMRec gNotification;
static Str255 gNotifyMessage;
static Boolean gNotificationPosted = FALSE;
short NotifyAlert( const short alertID, NTAlertFlags ntFlags )
{
// basically: if ( background ):
// install notification
// wait for app foreground
// delete notification
if ( ! gNotificationPosted )
{
if ( gApplication->InBackground())
{
// if an alert is to be displayed, set it up
if ( ntFlags & ntAlertDisplayMessage )
{
// build a string that says "The application “<name>” requires your attention.
// Please bring it to the front.
Str255 appName;
GetIndString( gNotifyMessage, kMiscStrListID, 17 );
gApplication->GetName( appName );
ConcatPStrings( gNotifyMessage, appName );
GetIndString( appName, kMiscStrListID, 18 );
ConcatPStrings( gNotifyMessage, appName );
gNotification.nmStr = gNotifyMessage;
}
else
gNotification.nmStr = NULL;
// set up any sound:
if ( ntFlags & ntAlertPlaySound )
gNotification.nmSound = ( Handle ) -1L;
else
gNotification.nmSound = NULL;
Handle appIconSuiteH;
OSErr theErr;
// set up icon and menu mark
gNotification.qType = 8;
gNotification.nmMark = TRUE;
gNotification.nmResp = NULL;
gNotification.nmRefCon = NULL;
theErr = GetIconSuite( &appIconSuiteH, kApplicationIconSuiteID, svAllAvailableData );
if ( theErr == noErr )
gNotification.nmIcon = appIconSuiteH;
else
gNotification.nmIcon = NULL;
// install notification:
NMInstall( &gNotification );
gNotificationPosted = TRUE;
// handle events until we come back to the front:
gApplication->WaitApplicationForeground();
// we're back, so delete the notification
NMRemove( &gNotification );
gNotificationPosted = FALSE;
if ( appIconSuiteH )
DisposeIconSuite( appIconSuiteH, FALSE );
}
return Alert( alertID, NULL );
}
else
{
// what can we do? This shouldn't arise in normal use, but just in case, we act as
// though the user saw an alert and clicked OK. Not ideal, but since a notification
// can't interrupt another, there's little choice.
SysBeep( 1 );
return ok;
}
}
// header invariant version of Delay():
void MZDelay( short ticks )
{
#ifdef __COMPATIBILITY__
long ignored;
#else
unsigned long ignored;
#endif
Delay( ticks, &ignored );
}
void MZWait( unsigned short ticks )
{
long tc = TickCount() + ticks;
while( TickCount() < tc )
gApplication->Process1Event();
}
void AssertErr( long lineNo, char* srcFile, char* reason, long val )
{
#if _DEBUG_
Str32 lineStr, valStr;
StringPtr fileStr, reasonStr;
NumToString( lineNo, lineStr );
NumToString( val, valStr );
fileStr = c2pstr( srcFile );
reasonStr = c2pstr( reason );
ParamText( lineStr, fileStr, reasonStr, valStr );
StopCursorAnimation();
(void) Alert( kAssertionAlertID, NULL );
FailOSErr( kSilentErr );
#endif
}
#if _USE_NAVIGATION_SERVICES
pascal void ZNavEventCallback( NavEventCallbackMessage cbMessage,
NavCBRecPtr cbParams,
NavCallBackUserData cbData )
{
switch ( cbMessage )
{
case kNavCBEvent:
// we are only interested in updates actually
if ( cbParams->eventData.event->what == updateEvt )
gApplication->Process1Event( cbParams->eventData.event );
break;
default:
break;
}
}
#endif